﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using Epi;

namespace CDC.SampleShell.Infrastructure
{
    public class Entity : IEntity
    {
        protected Dictionary<string, object> _data = new Dictionary<string, object>();
        protected Dictionary<string, object> _dataChanges = new Dictionary<string, object>();

        public Guid GlobalRecordId { get; protected internal set; }
        public string ForeignKey { get; internal set; }
        public View Form { get; protected internal set; }
        public string LastSaveLogonName { get; internal set; }
        public DateTime? LastSaveTime { get; internal set; }

        public IEnumerable<string> FieldNames
        {
            get
            {
                foreach (KeyValuePair<string, object> kvp in _data)
                {
                    yield return kvp.Key;
                }
            }
        }

        public Entity Parent { get; private set; }

        public virtual void OverwriteWith(IEntity record)
        {
            this.GlobalRecordId = record.GlobalRecordId;
            this.Form = record.Form;

            foreach (string fieldName in record.FieldNames)
            {
                this.SetData(fieldName, record.GetData(fieldName));
            }
        }

        public Entity(Entity record)
        {
            // pre
            Contract.Requires(record != null);

            OverwriteWith(record);
        }

        public Entity(Guid id, View form)
        {
            // pre
            Contract.Requires(id != null);
            Contract.Requires(form != null);

            GlobalRecordId = id;
            Form = form;
        }

        public Entity(Guid id, View form, Entity parent)
        {
            // pre
            Contract.Requires(id != null);
            Contract.Requires(form != null);
            Contract.Requires(parent != null);

            GlobalRecordId = id;
            Parent = parent;
            Form = form;
        }

        /// <summary>
        /// Adds a value change entry for record-keeping purposes
        /// </summary>
        /// <param name="fieldName">The name of the field whose value changed</param>
        /// <param name="originalValue">The original value of the field</param>
        /// <param name="newValue"></param>
        private void AddChange(string fieldName, object originalValue, object newValue)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(fieldName));

            // there is already an entry for this change
            if (_dataChanges.ContainsKey(fieldName))
            {
                // is the change being made a reversion back to the original value?
                if (originalValue == newValue)
                {
                    // if so, remove the change
                    _dataChanges.Remove(fieldName);
                }
            }
            // there is no entry for this change, so add
            else
            {
                _dataChanges.Add(fieldName, originalValue);
            }
        }

        /// <summary>
        /// Gets a data value for a given field in this record
        /// </summary>
        /// <param name="fieldName">The name of the field that contains the data</param>
        /// <returns>object</returns>
        public virtual object GetData(string fieldName)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(fieldName));

            return _data[fieldName];
        }

        /// <summary>
        /// Whether the record contains an entry for the specified field
        /// </summary>
        /// <param name="fieldName">The name of the field to check</param>
        /// <returns>bool; whether the field exists in this record</returns>
        public virtual bool ContainsField(string fieldName)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(fieldName));

            return _data.ContainsKey(fieldName);
        }

        /// <summary>
        /// Sets the data value for a given field in this record
        /// </summary>
        /// <param name="fieldName">The name of the field to update</param>
        /// <param name="value">The data value to set</param>
        public virtual void SetData(string fieldName, object value)
        {
            // pre
            Contract.Requires(!String.IsNullOrEmpty(fieldName));

            if (_data.ContainsKey(fieldName))
            {
                // the field/value pair exists

                // this is likely an update, so track the change
                AddChange(fieldName, _data[fieldName], value);
                
                // overwrite the exists data
                _data[fieldName] = value;
            }
            else
            {
                // the field/value pair does not exists, so add it
                _data.Add(fieldName, value);
            }
        }
    }
}
